
import Numeric as N
import re
import time

import Labware.tools as T
from PlateArray import PlateArray


class Victor2PlateArray( object ):
    """
    Parse a text file with assay results from a Victor3 Plate reader.
    """

    ex_comment = re.compile( '\s*\(.*\)\s*' )
    ex_protocol= re.compile( '(.*) \.{3,} (.*)$')


    def __init__(self):

        self.f = None
        self.values = {}
        self.runinfo = {}
        self.protocols = {}
        self.readoutkeys = []

        
    def __del__( self ):
        try:
            self.f.close()
        except:
            pass


    def parse( self, fname ):

        self.f = open( T.absfile( fname ), 'r')

        self.__parse_welltable()

        self.__parse_protocol()

        self.f.close()

        self.__convert_numbers()


    def toPlateArray( self ):

        r = PlateArray()

        vkeys = self.readoutkeys + ['time','repeat']

        for k in vkeys:
            r.setWells( k, self.values[k], self.values['well'] )

        r.protocols = self.protocols

        info = self.runinfo

        r.time = self.start_time
        r.repeat = info['Repeat']
        r.plate = info['Plate']
        r.temp_start = info['Start temp.']
        r.temp_stop  = info['End temp.']
        r.barcode = info['BarCode']

        return r

    def __split_tabbed( self, line, lower=False ):
        """
        Split tab-delimited str into items but filter out '', ' \n\r' etc.
        @param line: input line
        @type  line: str
        @rtype [ str ]
        """
        
        r = filter( str.split, line.split('\t') )

        r = [ re.sub('[\r\n]', '', x) for x in r ]

        if lower:
            r = [ x.lower() for x in r ]

        return r


    def __parse_welltable( self ):

        f = self.f

        header = self.__split_tabbed( f.readline(), lower=True )

        ## remove comments like "(CPS)"
        header = [ re.sub( self.ex_comment, '', h ) for h in header ]

        self.values = {}
        for h in header:
            self.values[ h ] = []

        ## every column following the time column is a result column
        self.readoutkeys = header[ header.index('time') : ]

        while 1:
            
            line = self.__split_tabbed( f.readline() )

            if line[0] == 'Plate':
                break

            assert len( line ) == len( header ), "columns don't match header"

            for h, v in zip( header, line ):

                self.values[h] += [v]
                
        self.__parse_runinfo( line )


    def __parse_runinfo( self, header ):
        """
        Parse the line following the readout table
        """

        line = self.__split_tabbed( self.f.readline() )
        
        assert len( header ) == len( line )

        self.runinfo = {}

        for h,v in zip( header, line ):
            self.runinfo[ h ] = v
        

    def __parse_protocol( self ):
        """
        Extract protocol descriptions.
        """
        self.protocols = {}
        
        for line in self.f:

            m = self.ex_protocol.match( line )

            if m:
                label, descr = m.groups()
                self.protocols[ label ] = re.sub( '\s', '', descr )


    def __abs_time( self, str_time, start ):
        """
        Convert string like 00:04:27.7 into absolute time since start.
        @type str_time: str
        @type start: time.struct_time
        @rtype: time.struct_time
        """
        h,m,s = str_time.split(':')
        r = int( h ) * 3600 + int( m ) * 60 + float( s )

        return time.mktime( start ) + r


    def __convert_numbers( self ):
        """
        Convert string items into times and numbers.
        """
        self.start_time = time.strptime( self.protocols['Measured on'],
                                         '%m/%d/%Y %I:%M:%S %p' )

        V = self.values  ## shortcut

        ## convert string time stamps into absolute time values
        start = self.start_time

        V['time'] = [ self.__abs_time( x, start ) for x in V['time'] ]

        ## convert readout values into numbers
        for k in self.readoutkeys:
            V[k] = N.array( [ float(x) for x in V[k] ] )

        ## split e.g. string 'A02' into a tuple ('A', 2)
        V['well']= [(c[0].lower(), int(c[1:])) for c in V['well']]


if __name__ == '__main__':

    v = Victor2PlateArray()

    v.parse ( T.testRoot() + '/PlateArray/victor3readout.txt' )

    p = v.toPlateArray()
